package neuralNetwork;


public class CTRNN {
	// genotype beskrivelse: 
	// gains*(hidden + output)
	// timeConstaints*(hidde + output)
	// weights se fig
	
//	private Map<Integer, CTRNNNeuron> neurons = new HashMap<Integer, CTRNNNeuron>();
	private CTRNNLayer inputLayer;
	private CTRNNLayer[] hiddenLayers;
	private CTRNNLayer outputLayer;
	
	public CTRNN(int[] networkLayout){
		hiddenLayers = new CTRNNLayer[networkLayout.length-2]; 
		outputLayer = new CTRNNLayer(null, networkLayout[networkLayout.length-1], false); 
		CTRNNLayer inFrontLayer = outputLayer; 
		for (int i = hiddenLayers.length - 1; i >= 0; i--){
			inFrontLayer = new CTRNNLayer(inFrontLayer, networkLayout[i+1], false);
			hiddenLayers[i] = inFrontLayer; 
		}
		inputLayer = new CTRNNLayer(inFrontLayer, networkLayout[0], true);
	}
	
	public double[] getWeights(){
		double[] weights = new double[nofWeights()]; 
		int index = 0; 
		index = ArrayOperations.insertData(weights, inputLayer.getWeights(), index); 
		for (CTRNNLayer layer : hiddenLayers) {
			index = ArrayOperations.insertData(weights, layer.getWeights(), index);
		}
		index = ArrayOperations.insertData(weights, outputLayer.getWeights(), index);
		return weights; 
	}
	
	public void setWeights(double[] weights){
//		resetActivation(); 
		
		int index = 0; 
		
		int nofHiddenNeurons = 0; 
		for (CTRNNLayer layer : hiddenLayers) {
			nofHiddenNeurons += layer.nofNeurons(); 
		}
		int nofOutputNeurons = outputLayer.nofNeurons(); 
		
		int neuronNr = 0; 
		int nofNeurons = inputLayer.nofNeurons();
		int neuronSize = inputLayer.getNeuronSize(); 
		double[][] neuronWeightList = new double[nofNeurons][];
		for (int i = 0; i < nofNeurons; i++) {
			double[] selectedWeights = ArrayOperations.selectData(weights, 3*nofHiddenAndOutputNeuron()+i*neuronSize, neuronSize);
			neuronWeightList[i] = createNeuronWeights(1, 1, 0, selectedWeights); 
		}
		inputLayer.setWeights(neuronWeightList); 
		
		neuronNr = inputLayer.nofNeurons(); 
		index = 3*nofHiddenAndOutputNeuron() + inputLayer.nofNeurons() * inputLayer.getNeuronSize();
		for (CTRNNLayer layer : hiddenLayers) {
			nofNeurons = layer.nofNeurons(); 
			neuronSize = layer.getNeuronSize(); 
			neuronWeightList = new double[nofNeurons][];
			for (int i = 0; i < nofNeurons; i++) {
				double[] selectedWeights = ArrayOperations.selectData(weights, index, neuronSize);
				index += selectedWeights.length;
				neuronWeightList[i] = createNeuronWeights(getGain(weights, i), getTimeConstant(weights, i), getBias(weights, i), selectedWeights); 
			}
			layer.setWeights(neuronWeightList); 
		}
		
		nofNeurons = outputLayer.nofNeurons(); 
		neuronSize = outputLayer.getNeuronSize(); 
		neuronWeightList = new double[nofNeurons][];
		for (int i = 0; i < nofNeurons; i++) {
			double[] selectedWeights = ArrayOperations.selectData(weights, index, neuronSize);
			index += selectedWeights.length;
			neuronWeightList[i] = createNeuronWeights(getGain(weights, i), getTimeConstant(weights, i), getBias(weights, i), selectedWeights); 
		}
		outputLayer.setWeights(neuronWeightList); 
		
	}
	
//	private void resetActivation() {
//		inputLayer.resetActivation();
//		for (CTRNNLayer layer : hiddenLayers){
//			layer.resetActivation();
//		}
//		outputLayer.resetActivation();
//		
//	}

	private double[] createNeuronWeights(double gain, double timeConstant, double bias, double[] weights){
		double[] neuron = new double[3+weights.length];
		neuron[0] = gain; 
		neuron[1] = timeConstant; 
		neuron[2] = bias;
		for (int i = 0; i < weights.length; i++) {
			neuron[3+i] = weights[i]; 
		}
		return neuron; 
	}
	
	private double getGain(double[] weights, int index){
		return weights[index]; 
	}
	
	private double getTimeConstant(double[] weights, int index){
		return weights[index + nofHiddenAndOutputNeuron()];
	}
	
	private double getBias(double[] weights, int index){
		return weights[index + 2*nofHiddenAndOutputNeuron()];
	}
	
	private int nofHiddenAndOutputNeuron(){
		int neurons = 0; 
		neurons += outputLayer.nofNeurons(); 
		for (CTRNNLayer hiddenLayer : hiddenLayers) {
			neurons += hiddenLayer.nofNeurons(); 
		}
		return neurons; 
	}
	
	public int nofWeights(){
		int nofWeights = 0;
		nofWeights += inputLayer.nofWeights(); 
		for (CTRNNLayer layer : hiddenLayers) {
			nofWeights += layer.nofWeights(); 
		}
		nofWeights += outputLayer.nofWeights(); 
		return nofWeights; 
	}
	
	public double[] update(boolean[] sensors){
		this.inputLayer.setInput(sensors); 
		fireUpdate(); 
		return outputLayer.returnOutput(); 
	}
	
	private void fireUpdate(){
		inputLayer.fireUpdate(); 
		for (CTRNNLayer layer : hiddenLayers) {
			layer.fireUpdate(); 
		}
		outputLayer.fireUpdate(); 
	}
	
	public String toString(){
		String theString = ""; 
		theString += "Sensors: " + inputLayer.toString(); 
		for (int i = 0; i < hiddenLayers.length; i ++){
			theString += "\nHiddenlayer" + Integer.toString(i) + ": " + hiddenLayers[i].toString(); 
		}
		theString += "\nOutputs: " + outputLayer.toString(); 
		return theString; 
	}
	
	public String activationString(){
		String theString = ""; 
		theString += "Sensors: " + inputLayer.activationString(); 
		for (int i = 0; i < hiddenLayers.length; i ++){
			theString += "\nHiddenlayer" + Integer.toString(i) + ": " + hiddenLayers[i].activationString(); 
		}
		theString += "\nOutputs: " + outputLayer.activationString(); 
		return theString; 
	}
	
	public static class ArrayOperations{
		
		public static double[] selectData(double[] data, int index, int size){
			double[] selection = new double[size]; 
			for (int i = 0; i < size; i++){
				selection[i] = data[index + i]; 
			}
			return selection; 
		}
		
		public static int insertData(double[] data, double[] insert, int index){
			for (int i = 0; i < insert.length; i++){
				data[index++] = insert[i]; 
			}
			return index; 
		}
		
		public static int insertData(boolean[] data, boolean[] insert, int index){
			for (int i = 0; i < insert.length; i++){
				data[index++] = insert[i]; 
			}
			return index; 
		}
	}
}
